<?php

hc_core_ClassLoader::load('substrate_stones_IContextStartupAware');
hc_core_ClassLoader::load('substrate_ContextUtil');
hc_core_ClassLoader::load('halo_HttpRequest');
hc_core_ClassLoader::load('halo_HttpResponse');
hc_core_ClassLoader::load('halo_ModelAndView');
hc_core_ClassLoader::load('halo_DispatcherUtil');
hc_core_ClassLoader::load('hc_logging_LogManager');
hc_core_ClassLoader::load('hc_core_ordered_OrderedUtil');

class halo_Dispatcher implements substrate_stones_IContextStartupAware {

    protected $logger;

    protected $context;

    protected $handlerAdapters;

    protected $handlerMappings;

    public function informAboutContextStartup(substrate_Context $context) {

        // Remember this.
        $this->context = $context;

        // Find all handler adapters.
        $this->handlerAdapters =
            hc_core_ordered_OrderedUtil::sort($context->findStonesByImplementation('halo_IHandlerAdapter'));

        // Find all handler mappings.
        $this->handlerMappings =
            hc_core_ordered_OrderedUtil::sort(
                $context->findStonesByImplementation(
                    'halo_IHandlerMapping'
                )
            );

        // Find all view resolvers.
        $this->viewResolvers =
            $context->findStonesByImplementation(
                'halo_IViewResolver'
            );

        // We can now ask for our logger.
        $this->logger = hc_logging_LogManager::get();

    }

    public function doService(halo_HttpRequest $httpRequest, halo_HttpResponse $httpResponse) {
        $originalHttpRequest = $httpRequest;
        $caughtException = false;
        $exception = null;
        ob_start();
        try {
            $this->doServiceInternal($httpRequest, $httpResponse);
        } catch (Exception $e) {
            $caughtException = true;
            $exception = $e;
        }
        if ( $caughtException ) {
            echo "Something bad happened.";
            while (ob_get_level()) {
                ob_end_clean();
            }
            header('HTTP/1.1 500 Internal Server Error');
            echo '<strong>500 Internal Server Error</strong><br />';
            echo 'An internal error has occurred.';
            $this->logger->error("Caught exception");
            $this->logger->error($exception->getMessage());
        }
        ob_flush();
    }
    
    protected function doServiceInternal(halo_HttpRequest $httpRequest, halo_HttpResponse $httpResponse) {

        halo_DispatcherUtil::SET_CONTEXT($httpRequest, $this->context);

        $handlerExecutionChain = $this->getHandlerExecutionChain($httpRequest);

        if ( $handlerExecutionChain === null or $handlerExecutionChain->getHandler() == null ) {
            throw new Exception('Unable to find a handler for request.');
        }

        $handler = $handlerExecutionChain->getHandler();

        $interceptorIdx = -1;
        foreach ( $handlerExecutionChain->getInterceptors() as $interceptor ) {
            $interceptorIdx++;
            if ( ! $interceptor->preHandle($httpRequest, $httpResponse, $handler) ) {
                $this->triggerAfterCompletion($handlerExecutionChain, $interceptorIdx, $httpRequest, $httpResponse);
                return;
            }
        }

        $handlerAdapter = $this->getHandlerAdapter($handler);

        if ( $handlerAdapter === null ) {
            throw new Exception('Unable to find a handler adapter for handler.');
        }

        $modelAndView = $handlerAdapter->handle($httpRequest, $httpResponse, $handler);

        foreach ( $handlerExecutionChain->getInterceptors() as $interceptor ) {
            $interceptor->postHandle($httpRequest, $httpResponse, $handler, $modelAndView);
        }

        $this->render($modelAndView, $httpRequest, $httpResponse);

        $this->triggerAfterCompletion($handlerExecutionChain, $interceptorIdx, $httpRequest, $httpResponse);
    }

    public function render(halo_ModelAndView $modelAndView = null, halo_HttpRequest $httpRequest, halo_HttpResponse $httpResponse) {
        if ( $modelAndView === null ) return;
        $view = null;
        if ( $modelAndView->isReferenced() ) {
            $view = $this->resolveViewName($modelAndView->getViewName(), $httpRequest, $httpResponse);
        } else {
            $view = $modelAndView->getView();
        }
        if ( $view === null ) {
            // What to do here? Technically this is not really an error... but how would
            // we raise this to the user without being obnoxious?

        } else {
            $viewContent = $view->render($modelAndView->getModel(), $httpRequest, $httpResponse);
            print $viewContent;
        }
    }

    public function getHandlerExecutionChain(halo_HttpRequest $httpRequest) {
        foreach ( $this->handlerMappings as $handlerMapping ) {
            $handler = $handlerMapping->getHandlerExecutionChain($httpRequest);
            if ( $handler !== null ) {
                return $handler;
            }
        }
        return null;
    }

    public function getHandlerAdapter($handler) {
        foreach ( $this->handlerAdapters as $handlerAdapter ) {
            if ( $handlerAdapter->supports($handler) ) {
                return $handlerAdapter;
            }
        }
        return null;
    }

    public function resolveViewName($viewName, halo_HttpRequest $httpRequest, halo_HttpResponse $httpResponse) {
        foreach ( $this->viewResolvers as $viewResolver ) {
            $view = $viewResolver->resolve($viewName, $httpRequest, $httpResponse);
            if ( $view !== null ) return $view;
        }
        return $view;
    }

    public function triggerAfterCompletion(halo_HandlerExecutionChain $handlerExecutionChain, $interceptorIdx, halo_HttpRequest $httpRequest, halo_HttpResponse $httpResponse, $exception = null) {
        if ( $handlerExecutionChain !== null ) {
            $interceptors = $handlerExecutionChain->getInterceptors();
            for ( $i = $interceptorIdx; $i >= 0; $i-- ) {
                $interceptor = $interceptors[$i];
                try {
                    $interceptor->afterCompletion($httpRequest, $httpResponse, $handlerExecutionChain->getHandler(), $exception);
                } catch (Exception $e) {
                    $this->logger->info('IHandlerInterceptor.afterCompletion() threw exception.');
                }
            }
        }
    }

}

?>
